export class APU{
    constructor(fc){
        this.fc = fc
        this.instance = null 
        this.pulse1 = new ChannelPulse(this)
        this.pulse2 = new ChannelPulse(this)
        // legthCounter的查表结果，提供给方波、三角波使用
        this.lengthCounterLoadValues = [
            10, 254, 20, 2,  40, 4,  80, 6,  160, 8,  60, 10, 14, 12, 26, 14,
            12, 16,  24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30
        ]

        // frameCounter的计数器，4步或5步模式中的变量
        this.frameCounterStep = 0
        // frameCounter触发IRQ的最大值，4步模式时是4，5步模式时是5
        this.frameCounterMax = 0
        // 0x4017相关标志
        // 4步或5步模式，受4017bit7控制
        this.frameCounterMode = null
        // IRQ禁止标志
        this.IRQInhibitFlag = null
        // 音频处理缓存
        this.audioProcessBuffer = null
        // apu每帧产生的原生波形
        this.rawOutput = new Float32Array(29829)
        // 每帧原生波形采样后的数据数组
        this.samplePreFrame = new Float32Array(735)
        // 用于将735个采样每帧写入，然后提供给音频api每次读取512的中间缓存
        // 这个缓存在test.js中定义，然后在apu的reset中传递进来
        this.audioProcessBuffer = null
        this.outputPos = 0

        this.cpuCycleMax = 29829
        this.currentPassedCycle = 0

        this.bufferWritePos = 0

        this.sampleCycleInterval = parseInt(29829/735)

    }

    static getInstance(){
        if(!this.instance){
            this.instance = new APU()
            return this.instance
        }
        else{
            return this.instance
        }
    }

    reset(audioProcessBuffer){
        // 将apu和音频api的公用缓存进行关联
        this.audioProcessBuffer = audioProcessBuffer
    }

    // APU的主执行方法，按照cpu执行的周期执行
    // APU的主频是CPU的一半，所以CPU执行2周期，APU执行1周期，但这样需要将CPU执行数除以2，不好计算
    // 因此这里按照CPU的周期来执行，但是把APU中和CPU周期相关的参数都乘了2倍（三角波主频除外）
    executeByCPUCycle(cpuCycle){
        // 比如cpu运行了5个周期，apu应该运行2.5周期，但这里也运行5周期
        for(let i=0;i<cpuCycle;i++){
            let pulse1Output = this.pulse1.cycle(this.currentPassedCycle)
            let pulse2Output = this.pulse2.cycle(this.currentPassedCycle)
            // 目前只合成方波1
            let mixedOutput = this.mix(pulse1Output,pulse2Output,0,0,0)
            this.rawOutput[this.currentPassedCycle] = mixedOutput
            this.currentPassedCycle++
            if(this.currentPassedCycle == this.cpuCycleMax){
                // 避免数组写入越界29829
                this.currentPassedCycle = this.cpuCycleMax - 1
            }
            // 驱动frameCounter
            this.clockFrameCounter()
        }    
    }

    // 提供一个采样方法给fc.js调用，采样实际是在每帧的所有cpu周期执行完之后，从29829个数据里采样
    // 采样后的数据放到中间数组供音频api获取
    sample(){
        let samples = this.rawOutput
        let samplePoint = 0
        for(let i=0;i<735;i++){
            samplePoint += this.sampleCycleInterval
            this.samplePreFrame[i] = this.rawOutput[samplePoint]
        }
        for(let i=0;i<735;i++){
            this.audioProcessBuffer[(this.bufferWritePos++) & 0xFFF ] = this.samplePreFrame[i]
        }
        // 每帧采样后重新设置经过的apu周期数
        this.currentPassedCycle = 0
    }



    mix(p1Output,p2Output,triOutput,noiseOutput,dmcOutput){
        // from https://wiki.nesdev.com/w/index.php/APU_Mixer
        let tnd = (
        0.00851 * triOutput +
        0.00494 * noiseOutput +
        0.00335 * dmcOutput
      )
      let pulse = 0.00752 * (p1Output + p2Output)
    //   return tnd + pulse
      return pulse
    }

    // 写状态寄存器4015
    writeControlReg(data){
        this.pulse1.isEnable = (data & 1) !== 0 ? 1 : 0
        // 清除使能位时重置lengthCounter
        if(!this.pulse1.isEnable){
            this.pulse1.lengthCounterValue = 0
        }
        this.pulse2.isEnable = (data & 2) !== 0 ? 1 : 0
        if(!this.pulse2.isEnable){
            this.pulse2.lengthCounterValue = 0
        }
    }

    // 读状态寄存器4015，返回各通道状态
    readControlReg(){
        let data = 0
        data = data | this.pulse1.getLengthCounterStatus
        data = data | (this.pulse2.getLengthCounterStatus > 0) ? 0x2 : 0
        return data & 0xffff
    }

    setFrameCounterStatus(value){
        this.frameCounterMode = (value >> 7) & 1
        if(this.frameCounterMode == 0){
            // 4步模式，设置相关标志
            this.frameCounterMax = 4
        }
        else{
            // 5步模式
            this.frameCounterMax = 5
        }
        this.IRQInhibitFlag = (value >> 6) & 1
        {

        }
    }

    clockFrameCounter(){
        this.frameStepCount++
        if(this.frameStepCount >= 7457){
            this.runFrameCounter()
        }
        this.frameStepCount = 0
    }

    // 驱动240Hz时钟动作，用来修改各通道的包络、音量、长度等自动递减或改变值（查表）
    // 按照4步或5步模式运行
    runFrameCounter(){
        this.frameCounterStep++
        // 超过一个模式周期后重置frameCounterStep
        if (this.frameCounterStep >= this.frameCounterMax) {
            this.frameCounterStep = 0
          }
        if(this.frameCounterMode == 0){
            switch (this.frameCounterStep){
                case 0:
                    this.processEnvelopeAndLinearCounter()
                    break
                case 1:
                    this.processLengthCounterAndSweep()
                    this.processEnvelopeAndLinearCounter()
                    break
                case 2:
                    this.processEnvelopeAndLinearCounter()
                    break
                case 3:
                    this.triggerIRQ()
                    this.processLengthCounterAndSweep()
                    this.processEnvelopeAndLinearCounter()
                    break
            }
        }
        else{
            switch (this.frameCounterStep){
                case 0:
                    this.processEnvelopeAndLinearCounter()
                    break
                case 1:
                    this.processLengthCounterAndSweep()
                    this.processEnvelopeAndLinearCounter()
                    break
                case 2:
                    this.processEnvelopeAndLinearCounter()
                    break
                case 3:
                    break
                case 4:
                    this.processLengthCounterAndSweep()
                    this.processEnvelopeAndLinearCounter()
                    break
            }
        }
    }

    
    // 驱动包络,4步模式每步都调用
    processEnvelopeAndLinearCounter(){
        // envelopeStart标志为cleard(0)，驱动envelopeCounter
        if(this.pulse1.envelopeStart == 0) {
            if(this.pulse1.envelopeCounter !== 0) {
              this.pulse1.envelopeCounter--
            } else {
                // envelopeCounter倒数到0后会重置
                this.pulse1.envelopeCounter = this.pulse1.volume
                // 修改音量衰减值
                if(this.pulse1.decay !== 0) {
                    this.pulse1.decay--
                } else {
                    // 衰减到0后，判断是否循环衰减
                    if(this.pulse1.lengthCounterHalt) {
                    this.pulse1.decay = 15
                    }
                }
            }
        }else {
            //envelopeStart标志为1,重置decay值为15，并将envelopeStart置0，包络计数器重置，开始衰减
            this.pulse1.envelopeStart = 0
            this.pulse1.decay = 15
            this.pulse1.envelopeCounter = this.pulse1.volume
        }
        if(this.pulse2.envelopeStart == 0) {
            if(this.pulse2.envelopeCounter !== 0) {
              this.pulse2.envelopeCounter--
            } else {
                this.pulse2.envelopeCounter = this.pulse2.volume
                if(this.pulse2.decay !== 0) {
                    this.pulse2.decay--
                } else {
                    if(this.pulse2.lengthCounterHalt) {
                    this.pulse2.decay = 15
                    }
                }
            }
        }else {
            this.pulse2.envelopeStart = 0
            this.pulse2.decay = 15
            this.pulse2.envelopeCounter = this.pulse2.volume
        }
    }

    // 驱动长度计数器,每2步一次
    processLengthCounterAndSweep(){
        //4步模式每两步修改lengthCounter
        if(!this.pulse1.lengthCountHalt && this.pulse1.lengthCounterValue !== 0) {
            this.pulse1.lengthCounterValue--
        }
        if(!this.pulse2.lengthCountHalt && this.pulse2.lengthCounterValue !== 0) {
            this.pulse2.lengthCounterValue--
        }
        // 处理扫频
        if(
            this.pulse1.sweepTimer === 0 && this.pulse1.sweepEnable &&
            !this.pulse1.sweepMuting && this.pulse1.sweepShiftCount > 0
          ) {
            this.pulse1.timer = this.pulse1.sweepTarget
            this.pulse1.updateSweepP1()
        }
        if(this.pulse1.sweepTimer === 0 || this.pulse1.sweepReloadFlag) {
            this.pulse1.sweepTimer = this.pulse1.sweepPeriod
            this.pulse1.sweepReloadFlag = false
        } else {
            this.pulse1.sweepTimer--
        }
    }

    // 触发中断
    triggerIRQ(){
        this.fc.setIRQ()
    }

    writeChannel(address,data){
        switch(address){
            case 0x4000:
                this.pulse1.dutyType = (data & 0xC0) >> 6 //bit7、bit6
                this.pulse1.lengthCountHalt = (data & 0x20) > 5 //bit5
                this.pulse1.constantVolume = (data & 0x10) > 4 //bit4
                this.pulse1.volume = data & 0xF //bit3-bit0
                break
            case 0x4001:
                this.pulse1.sweepEnable = (data & 0x80) > 7
                this.pulse1.sweepPeriod = (data & 0x70) >> 4 //bit6-bit4
                this.pulse1.sweepNegate = (data & 0x8) > 3
                this.pulse1.sweepShiftCount = data & 0x7
                // side effects
                this.pulse1.sweepReloadFlag = true
                break
            case 0x4002:
                // 只修改timer的低8位
                this.pulse1.timer &= 0x700
                this.pulse1.timer |= data
                this.pulse1.updateSweepP1()
                break
            case 0x4003:
                this.pulse1.timer &= 0xFF
                this.pulse1.timer |= (data & 0x7) << 8 //取bit2-bit0，组合
                if(this.pulse1.isEnable) {
                    // 查表获取lengthCounter
                    this.pulse1.lengthCounterValue = this.lengthCounterLoadValues[(data & 0xf8) >> 3] //bit7-bit3,查表
                  }
                // side effects
                this.pulse1.dutyIndex = 0
                this.pulse1.envelopeStart = true
                break
            case 0x4004:
                this.pulse2.dutyType = (data & 0xC0) >> 6 //bit7、bit6
                this.pulse2.lengthCountHalt = (data & 0x20) > 5 //bit5
                this.pulse2.constantVolume = (data & 0x10) > 4 //bit4
                this.pulse2.volume = data & 0xF //bit3-bit0
                break
            case 0x4005:
                this.pulse2.sweepEnable = (data & 0x80) > 7
                this.pulse2.sweepPeriod = (data & 0x70) >> 4 //bit6-bit4
                this.pulse2.sweepNegate = (data & 0x8) > 3
                this.pulse2.sweepShiftCount = data & 0x7
                // side effects
                this.pulse2.sweepReloadFlag = true
                break
            case 0x4006:
                // 只修改timer的低8位
                this.pulse2.timer &= 0x700
                this.pulse2.timer |= data
                break
            case 0x4007:
                this.pulse2.timer &= 0xFF
                this.pulse2.timer |= (data & 0x7) << 8 //取bit2-bit0，组合
                if(this.pulse2.isEnable) {
                    // 查表获取lengthCounter
                    this.pulse2.lengthCounter = this.lengthCounterLoadValues[(data & 0xf8) >> 3] //bit7-bit3,查表
                  }
                // side effects
                this.pulse2.dutyIndex = 0
                this.pulse2.envelopeStart = true
                break
            default:
        }
    }


}

class ChannelPulse{
    constructor(apu){
        this.apu = apu

        // 波形占空比
        // 方波占空比类型 $4000:bit7-bit6 / $4004:bit7-bit6
        this.dutyType = 0
        // 当前占空比位置索引
        this.dutyIndex = 0
        // 波形占空比查表
        this.dutyTable = [
            [0, 1, 0, 0, 0, 0, 0, 0],
            [0, 1, 1, 0, 0, 0, 0, 0],
            [0, 1, 1, 1, 1, 0, 0, 0],
            [1, 0, 0, 1, 1, 1, 1, 1]
        ]

        // 波形时长
        // 长度计数器挂起标志 $4000:bit5 / $4004:bit5
        // 同时用于envelope loop
        // 为0代表lengthCounterHalt，为1代表envelope loop，读取它的函数不同
        this.lengthCounterHalt = 0
        // 长度计数器 $4003:bit7-bit3 / $4007:bit7-bit3
        this.lengthCounter = 0
        // 保存查表后长度计数器的实际值
        this.lengthCounterValue = 0

        // 波形频率
        // 波形频率计数器 $4003:bit2-bit0 $4002:bit7-bit0 / $4007:bit2-bit0 $4006:bit7-bit0
        // timer的值是寄存器数值的2倍，原因是apu按照cpu的频率执行了
        this.timer = 0
        // timer计时倒数临时变量
        this.timerCountDown = 0

        //音量和包络 
        // 固定音量或包络音量标志位 $4000:bit4 / $4004:bit4
        // (0: use volume from envelope  1: use constant volume)
        this.constantVolume =0
        // 音量数值 $4000:bit3-bit0 / $4004:bit3-bit0
        this.volume = 0
        // 包络重置标志，不通过寄存器设置，受frameCounter控制自动设置
        this.envelopeStartFlag = 0
        // 包络衰减音量的当前值
        this.decay = 0
        // envelopeCounter，实际和volume共用bit位$4000:bit3-bit0 / $4004:bit3-bit0
        this.envelopeCounter = 0
        
        // 使能标志
        // 方波使能标志，$4015:bit1-bit0 ,bit0为方波1,bit1为方波2
        this.isEnable = false

        // 扫频
        // 扫频使能标志 $4001:bit7 / $4005:bit7
        this.sweepEnable = 0
        // 扫频周期 $4001:bit6-bit4 / $4005:bit6-bit4
        this.sweepPeriod = 0
        // 扫频取消标志 $4001:bit3 / $4005:bit3
        this.sweepNegate = 0
        // 偏移计数器 $4001:bit2-bit0 / $4005:bit2-bit0
        this.sweepShiftCount = 0

        this.sweepReloadFlag = false
        this.sweepTimer = 0
        this.sweepTarget = 0
        this.sweepMuting = false

    }

    getLengthCounterStatus(){
        if(this.lengthCounter === 0 || !this.isEnable){
            return 0
        }
        else{
            return 1
        }
    }

    // 执行一定的cpu周期
    cycle(){
        if(this.timerCountDown != 0){
            this.timerCountDown--
        }
        else{
            // timer的实际倒数值应该乘2,因为这里是按照CPU频率计算的
            this.timerCountDown = (this.timer * 2) + 1
            this.dutyIndex++
            // 让dutyIndex在0-7之间循环
            this.dutyIndex = this.dutyIndex & 0x7
        }
        let dutyOutput = this.dutyTable[this.dutyType][this.dutyIndex]
        let pulseOutput
        if(dutyOutput == 0 || this.lengthCounterValue == 0) {
            pulseOutput = 0
          } else {
            // 判断使用包络音量还是常量音量
            pulseOutput = this.constantVolume ? this.volume : this.decay
          }
        return pulseOutput
    }

    updateSweepP1() {
        let change = this.timer >> this.sweepShiftCount
        if(this.sweepNegate) {
          change = (-change) - 1
        }
        this.sweepTarget = this.timer + change
        if(this.sweepTarget > 0x7ff || this.timer < 8) {
          this.sweepMuting = true
        } else {
          this.sweepMuting = false
        }
    }
    

}